#include <iostream>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;

//我们当前创建子进程的目的:
//通过if else分流让子进程执行父进程代码的一部分
//那么我要子进程执行一个"全新的程序"呢?
//那么这个时候就需要进程的程序替换了

//程序的本质就是一个文件
//文件=程序代码+程序数据

//那么进程程序替换就是进程不变,仅仅替换当前进程的代码和数据
//是没有创建任何新的进程的

//进程替换原理
//用fork创建子进程后,执行的是和父进程相同的程序(但是有可能执行不同的代码分支)
//子进程往往要调用exec系列的函数去执行另一个程序
//当进程调用exec一系列的函数时
//该进程的用户空间代码和数据完全被新程序替换
//就是把磁盘里的代码和数据加载到之前的进程物理内存上的数据段和代码段
//虽然子进程和父进程是共用的代码段和数据段,但是当子进程需要改变的时候
//就会发生写时拷贝,然后子进程加载到物理内存上另一个地方
//然后将子进程的数据段和代码段进行了替换,可能代码和数据量和之前的程序不同
//所以页表的映射关系,也会有一些变化,其他的保持不变

//exec系列函数的解释
//如果这些函数调用成功,则加载新的程序,从启动代码开始执行,不再返回
//如果调用错误则返回-1
//所以exec系列函数只有出错的返回值而没有成功的返回值

//exec系列函数的命名规律
//l(list):表示参数采用列表
//v(vector):表示参数采用数组
//p(path):有p表示自动搜索环境变量PATH
//e(environment):表示自己维护环境变量

//所以为什么会有exec系列的函数接口,看起来没有多大的差别,只是参数不同而已
//是为了满足不同的应用场景的

int main()
{
    cout << "i am process! pid: " << getpid() << endl;
    if (fork() == 0)
    {
        //1.execl
        //execl("/usr/bin/ls" /*这里是你要执行目标程序的所在路径,包括了程序名称*/,"ls", "-a", "-l", nullptr/*这里是用了可变参数列表,要执行的目标程序在命令行上怎么运行,这里参数就怎么一个一个传递进去,最后必须以nullptr作为参数传递的结束*/);
       
        //这里用数组来存储怎么执行程序
        char* argv1[] = {"ls", "-a", "-l", nullptr};
        char* argv2[] = {"ps", "axj", nullptr};
        char* argv3[] = {"myexe", nullptr};
        char* argv4[] = {"python", "test.py", nullptr};

        //2.execv
        //execv("/usr/bin/ls", argv/*这里不传入列表,而是传入数组,但是和数组里面的内容和列表差不多*/);

        //3.execlp
        //execlp("sl"/*可以自动推断环境变量*/, "sl", nullptr);
        
        //4.execvp
        //execvp("ps", argv2);
        
        //这里的是需要你自己控制的环境变量
        char* env[] = {"liukun", "qingdiaoshen", nullptr};

        //5.execle
        //execle("./myexe", "myexe", nullptr, env/*可以自己控制环境变量*/);
        
        //6.execve
        //execve("./myexe", argv3, env);

        //7.execvpe
        execvpe("/usr/bin/python3", argv4, env);
        

        //只要进程的程序替换成功了,就不会执行后续代码
        //意味着exec系列函数,成功的时候,不需要返回值检测
        cout << "hahahahahahha" << endl;
        cout << "hahahahahahha" << endl;
        cout << "hahahahahahha" << endl;
        cout << "hahahahahahha" << endl;
        cout << "hahahahahahha" << endl;
        cout << "hahahahahahha" << endl;
        //如果执行成功了,是直接替换了程序的,失败了才有返回值,所以返回1
        exit(1);
    }
    waitpid(-1, nullptr, 0);
    cout << "wait success!" << endl;
    
    return 0;
}

//事实上,只有execve是真正的系统调用,其他的六个函数都是对execve的封装
//最终还是调用的是execve函数,所以这也解释了为啥execve在man手册的第二节
//其他函数在man手册的第三节

//我们来联想一下函数和进程之间的相似性
//exec/exit就像call/return 
//一个C程序有很多函数组成。一个函数可以调用另外一个函数
//同时传递给它一些参数。被调用的函数执行一定的操作
//然后返回一个值。每个函数都有他的局部变量
//不同的函数通过call/return系统进行通信。
//这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础
//Linux鼓励将这种应用于程序之内的模式扩展到程序之间
//一个C程序可以fork/exec另一个程序，并传给它一些参数
//这个被调用的程序执行一定的操作，然后通过exit(n)来返回值调用它的进程
//以通过wait(&status)来获取exit的返回值。
